using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Gko.Utils
{
	/// <summary>
	/// Represents unmanaged memory block or pointer with automatic disposing
	/// </summary>
	public class UnmanagedMemoryBlock : UnmanagedPointer
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="T:Gko.Utils.UnmanagedMemoryBlock"/> class.
		/// </summary>
		/// <param name="bytesCount">The count of bytes to allocate.</param>
		public UnmanagedMemoryBlock(int bytesCount)
			: this(Marshal.AllocHGlobal(bytesCount), bytesCount, Marshal.FreeHGlobal)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Gko.Utils.UnmanagedMemoryBlock"/> class.
		/// </summary>
		/// <param name="value">The pointer to unmanaged memory block.</param>
		/// <param name="bytesCount">The count of bytes.</param>
		/// <param name="disposeAction">The dispose action.</param>
		public UnmanagedMemoryBlock(IntPtr value, int bytesCount, Action<IntPtr> disposeAction)
			: base(value, disposeAction)
		{
			this.Size = bytesCount;
		}


		/// <exception cref="InvalidOperationException">Throws if have not null pointer (not equals to <see cref="F:System.IntPtr.Zero"/>)</exception>
		/// <exception cref="ArgumentNullException">One of arguments is <c>null</c>.</exception>
		public UnmanagedMemoryBlock Attach(IntPtr value, int bytesCount, Action<IntPtr> disposeAction)
		{
			this.Attach(value, disposeAction);
			this.Size = bytesCount;
			return this;
		}

		/// <exception cref="InvalidOperationException">Throws if have not null pointer (not equals to <see cref="F:System.IntPtr.Zero"/>)</exception>
		/// <exception cref="ArgumentNullException">One of arguments is <c>null</c>.</exception>
		public UnmanagedMemoryBlock Attach(byte[] value)
		{
			this.Attach(Marshal.AllocHGlobal(value.Length), value.Length, Marshal.FreeHGlobal);
			Marshal.Copy(value, 0, this, this.Size);
			return this;
		}

		/// <exception cref="InvalidOperationException">Throws if have not null pointer (not equals to <see cref="F:System.IntPtr.Zero"/>)</exception>
		/// <exception cref="ArgumentNullException">One of arguments is <c>null</c>.</exception>
		public UnmanagedMemoryBlock Attach(byte[] value, int startIndex, int length)
		{
			this.Attach(Marshal.AllocHGlobal(value.Length), value.Length, Marshal.FreeHGlobal);
			Marshal.Copy(value, startIndex, this, Math.Min(this.Size, startIndex + length));
			return this;
		}

		public override IntPtr Detach()
		{
			using (Scope.Create(() => this.Size = 0)) {
				return base.Detach();
			}
		}


		/// <summary>
		/// Gets the size.
		/// </summary>
		/// <value>The size.</value>
		public int Size
		{
			get;
			private set;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Gko.Utils.UnmanagedMemoryBlock"/> class from bytes array.
		/// </summary>
		/// <param name="value">The bytes.</param>
		/// <returns>New instance of the <see cref="T:Gko.Utils.UnmanagedMemoryBlock"/> class</returns>
		public static UnmanagedMemoryBlock FromBytes(byte[] value)
		{
			var block = new UnmanagedMemoryBlock(value.Length);
			Marshal.Copy(value, 0, block, block.Size);
			return block;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Gko.Utils.UnmanagedMemoryBlock"/> class from bytes array.
		/// </summary>
		/// <param name="value">The bytes.</param>
		/// <param name="startIndex">The zero-based index into the array where FromBytes should start.</param>
		/// <param name="length">The number of array elements to copy.</param>
		/// <returns>New instance of the <see cref="T:Gko.Utils.UnmanagedMemoryBlock"/> class</returns>
		public static UnmanagedMemoryBlock FromBytes(byte[] value, int startIndex, int length)
		{
			var block = new UnmanagedMemoryBlock(value.Length);
			Marshal.Copy(value, startIndex, block, Math.Min(block.Size, startIndex + length));
			return block;
		}


		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "allocator")]
		public static UnmanagedMemoryBlock Allocate(int size, Func<int, IntPtr> allocator, Action<IntPtr> disposeAction)
		{
			return new UnmanagedMemoryBlock(allocator(size), size, disposeAction);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Co"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Mem")]
		public static UnmanagedMemoryBlock AllocateCoTaskMem(int size)
		{
			return Allocate(size, Marshal.AllocCoTaskMem, Marshal.FreeCoTaskMem);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Co"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Mem"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public static UnmanagedMemoryBlock AllocateCoTaskMem<T>()
		{
			return AllocateCoTaskMem(typeof(T));
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Co"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Mem")]
		public static UnmanagedMemoryBlock AllocateCoTaskMem(Type type)
		{
			return AllocateCoTaskMem(Marshal.SizeOf(type));
		}

		public static UnmanagedMemoryBlock AllocateHGlobal(int size)	
		{
			return Allocate(size, Marshal.AllocHGlobal, Marshal.FreeHGlobal);
		}

		/// <summary>
		/// Converts memory block to bytes array.
		/// </summary>
		/// <returns>Bytes</returns>
		public byte[] ToBytes()
		{
			return this.ToBytes(this.Size);
		}

		/// <summary>
		/// Converts memory block to bytes array.
		/// </summary>
		/// <param name="size">The size.</param>
		/// <returns>Bytes</returns>
		public byte[] ToBytes(int size)
		{
			this.VerifyNotDisposed();
			if (size > 0) {
				var bytes = new byte[size];
				Marshal.Copy(this, bytes, 0, size);
				return bytes;
			}
			return null;
		}

		protected override void OnDisposeUnmanaged()
		{
			base.OnDisposeUnmanaged();
			this.Size = 0;
		}

		public UnmanagedMemoryStream ToStream(bool canRead, bool canWrite)
		{
			return new UnmanagedMemoryStream(this, this.Size, canRead, canWrite);
		}
	}
}